﻿#pragma once

#include <shared_mutex>
#include <array>

#include "../../Memory/GameData.h"
#include "../Setting/SettingManager.h"

#include <json/nlohmannjson.h>

enum class Category : uint8_t {
	COMBAT = 0,
	VISUAL = 1,
	MOVEMENT = 2,
	PLAYER = 3,
	WORLD = 4,
	MISC = 5,
	CUSTOM = 6,
	CLIENT = 7
};

constexpr std::array<std::u8string_view, 8> CategoryName{
	u8"COMBAT",
	u8"VISUAL",
	u8"MOVEMENT",
	u8"PLAYER",
	u8"WORLD",
	u8"MISC",
	u8"CUSTOM",
	u8"CLIENT"};

struct EnumEntry {
	std::array<std::u8string_view, languagesize> name{};
	std::array<std::u8string_view, languagesize> annotation{};
};

struct SettingEnum {
	fast_io::vector<EnumEntry> Entrys{};

	int selected = -1;  // value in use
};

enum class ValueType : uint8_t {
	FLOAT_T,
	INT_T,
	BOOL_T,
	ENUM_T
};

struct SettingValue {
	float _float{};
	int _int{};
	bool _bool{};
};

struct p_SettingValue {
	union {
		float* _float{};
		int* _int;
		bool* _bool;
		SettingEnum* Enum;
	};
};

struct SettingEntry {
	std::array<std::u8string_view, languagesize> name{};
	std::array<std::u8string_view, languagesize> annotation{};

	ValueType valueType{};   // FLOAT_T,INT_T,BOOL_T,ENUM_T
	p_SettingValue value{};  // value in use
	SettingValue defaultValue{};
	SettingValue minValue{};
	SettingValue maxValue{};
	bool render = true;

	constexpr SettingEntry() = default;

	constexpr SettingEntry(std::array<std::u8string_view, languagesize> n, float* floatPtr, float _defaultValue, float _minValue, float _maxValue, std::array<std::u8string_view, languagesize> anno = std::array<std::u8string_view, languagesize>{})
		: name(std::move(n)), annotation(std::move(anno)), valueType(ValueType::FLOAT_T) {
		value._float = floatPtr;
		defaultValue._float = _defaultValue;
		minValue._float = _minValue;
		maxValue._float = _maxValue;
	}

	constexpr SettingEntry(std::array<std::u8string_view, languagesize> n, int* intPtr, int _defaultValue, int _minValue, int _maxValue, std::array<std::u8string_view, languagesize> anno = std::array<std::u8string_view, languagesize>{})
		: name(std::move(n)), annotation(std::move(anno)), valueType(ValueType::INT_T) {
		value._int = intPtr;
		defaultValue._int = _defaultValue;
		minValue._int = _minValue;
		maxValue._int = _maxValue;
	}

	constexpr SettingEntry(std::array<std::u8string_view, languagesize> n, bool* boolPtr, bool _defaultValue, std::array<std::u8string_view, languagesize> anno = std::array<std::u8string_view, languagesize>{})
		: name(std::move(n)), annotation(std::move(anno)), valueType(ValueType::BOOL_T) {
		value._bool = boolPtr;
		defaultValue._bool = _defaultValue;
	}

	constexpr SettingEntry(std::array<std::u8string_view, languagesize> n, SettingEnum* ptr, int _defaultValue, std::array<std::u8string_view, languagesize> anno = std::array<std::u8string_view, languagesize>{})
		: name(std::move(n)), annotation(std::move(anno)), valueType(ValueType::ENUM_T) {
		value.Enum = ptr;
		defaultValue._int = _defaultValue;
	}

	constexpr void makeSureTheValueIsAGoodBoiAndTheUserHasntScrewedWithIt() {
		switch (valueType) {
		case ValueType::ENUM_T:
			value.Enum->selected = std::max(0, std::min(static_cast<int>(value.Enum->Entrys.size()) - 1, value.Enum->selected));
			break;
		case ValueType::BOOL_T:
			break;
		case ValueType::FLOAT_T:
			*value._float = std::max(minValue._float, std::min(maxValue._float, *value._float));
			break;
		case ValueType::INT_T:
			*value._int = std::max(minValue._int, std::min(maxValue._int, *value._int));
			break;
		default:
			break;
		}
	}
};

class IModule {
	friend class ModuleManager;

public:
	Category category{};
	std::array<std::u8string_view, languagesize> name{};
	std::array<std::u8string_view, languagesize> tooltip{};

	fast_io::vector<SettingEntry> settings{};

protected:
	void (*init)(IModule*) = nullptr;
	void (*onEnable)() = nullptr;
	void (*onDisable)() = nullptr;
	void (*onWorldTick)(C_GameMode*) = nullptr;
	void (*onTick)(C_GameMode*) = nullptr;
	void (*onKeyUpdate)(uint8_t, bool) = nullptr;
	void (*onAttack)(C_Entity*) = nullptr;
	void (*onPreRender)(C_MinecraftUIRenderContext*) = nullptr;
	void (*onPostRender)(C_MinecraftUIRenderContext*) = nullptr;
	void (*onLevelRender)() = nullptr;
	void (*onMove)(C_MoveInputHandler*) = nullptr;
	void (*onSendPacket)(C_Packet*, bool&) = nullptr;
	void (*onPlayerTick)(C_Player*) = nullptr;
	void (*onGetPickRange)() = nullptr;
	void (*onUpdate)() = nullptr;

private:
	bool last_enbale = false;

private:
	bool enabled = false;
	int keybind = 0;
	std::shared_mutex iModuleLook{};

public:
	// IModule(void (*_init)(IModule*) )  : init(_init) {}
	IModule(Category c,
			std::array<std::u8string_view, languagesize> n,
			std::array<std::u8string_view, languagesize> t,
			int key = 0,
			fast_io::vector<SettingEntry> s = fast_io::vector<SettingEntry>{},
			void (*_init)(IModule*) = nullptr,
			void (*_onEnable)() = nullptr,
			void (*_onDisable)() = nullptr,
			void (*_onWorldTick)(C_GameMode*) = nullptr,
			void (*_onTick)(C_GameMode*) = nullptr,
			void (*_onKeyUpdate)(uint8_t, bool) = nullptr,
			void (*_onAttack)(C_Entity*) = nullptr,
			void (*_onPreRender)(C_MinecraftUIRenderContext*) = nullptr,
			void (*_onPostRender)(C_MinecraftUIRenderContext*) = nullptr,
			void (*_onLevelRender)() = nullptr,
			void (*_onMove)(C_MoveInputHandler*) = nullptr,
			void (*_onSendPacket)(C_Packet*, bool&) = nullptr,
			void (*_onPlayerTick)(C_Player*) = nullptr,
			void (*_onGetPickRange)() = nullptr,
			void (*_onUpdate)() = nullptr);

public:
	void _init();
	void _onEnable();
	void _onDisable();
	uint8_t getKeybind() const;
	void setKeybind(uint8_t key);
	void _onWorldTick(C_GameMode*);
	void _onTick(C_GameMode*);
	void _onKeyUpdate(uint8_t key, bool isDown);
	void _onAttack(C_Entity*);
	void _onPreRender(C_MinecraftUIRenderContext* renderCtx);
	void _onPostRender(C_MinecraftUIRenderContext* renderCtx);
	void _onLevelRender();
	void _onMove(C_MoveInputHandler*);
	void onLoadConfig(nlohmann::json& conf);
	void onSaveConfig(nlohmann::json& conf);
	void setEnabled(bool enabled);
	void toggle();
	bool isEnabled() const;
	void _onSendPacket(C_Packet*, bool&);
	void _onPlayerTick(C_Player*);
	void _onGetPickRange();
	void _onUpdate();
};
